package com.yangjie.common.service;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import com.yangjie.common.DomainUtils;


@Aspect
public class AccessArbiter {
	Logger logger = Logger.getLogger(this.getClass());

	
	public String expectedAuthority(String domainName, String methodName) {
		if(methodName.startsWith("create")) {
			return "CREATE_" + domainName.toUpperCase();
		} else if(methodName.startsWith("delete")){
			return "DELETE_" + domainName.toUpperCase();
		} else if(methodName.startsWith("update")){
			return "UPDATE_" + domainName.toUpperCase();
		} else if(methodName.startsWith("get") || methodName.startsWith("find") || methodName.startsWith("count") || methodName.startsWith("exists")){
			return "READ_" + domainName.toUpperCase();
		} 
		
		return null;
	}

	@Before("this(com.yangjie.common.service.Service) and not (this(com.yangjie.common.syslog.SysLogService) and execution(* create(..)))")
	public void before(JoinPoint joinPoint) throws AccessDeniedException {
		String methodName = joinPoint.getSignature().getName();
		Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();

		if(method.getDeclaringClass().isInterface()) {
			try {
				method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes());				
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		Annotation preAuthorizeAnnotation = method.getAnnotation(PreAuthorize.class);
		if(preAuthorizeAnnotation != null) {
			return ;
		}
//		Annotation[] annotations = method.getAnnotations();
//		for(Annotation annotation: annotations) {
//			logger.debug(annotation.getClass());
//			if(annotation.equals(PreAuthorize.class)) {
//				return;
//			}
//		}
		String className = joinPoint.getTarget().getClass().getName();
		logger.debug("AccessArbiter before.........." + className + "; method:" + methodName);

		
		if(methodName.equals("findUserByUsername")) {
			return;
		}
		SecurityContext securityContext = SecurityContextHolder.getContext();
		if(securityContext == null) {
			throw new AccessDeniedException("please login first");
		}
		Authentication authentication = securityContext.getAuthentication();
		if(authentication == null) {
			throw new AccessDeniedException("please login first");
		}
		if(authentication instanceof AnonymousAuthenticationToken) {
			throw new AccessDeniedException("please login first");
		}
		if(!authentication.isAuthenticated()) {
			throw new AccessDeniedException("please login first");
		}
		if(authentication.getName().equals("admin")) {
			return;
		}
		
		String domainName = DomainUtils.getDomainName(className);
		String expectedAuthoriy = expectedAuthority(domainName, methodName);
		if(expectedAuthoriy == null) {
			throw new AccessDeniedException("method " + methodName + " break the rule of how to name CRUD method");
		}
		for(GrantedAuthority authority:authentication.getAuthorities()) {
			if(authority.getAuthority().equals(expectedAuthoriy)) {
				return;
			}
		}
		logger.debug("need permisssion " + expectedAuthoriy);
		throw new AccessDeniedException("please login as user who has permission");
		
	}
//	public void grantAccess() {
//		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//		Collection<GrantedAuthority> authority = authentication.getAuthorities();
//	}
}
